package brickdestroyer.simulation;

import brickdestroyer.util.ImageResources;
import java.awt.Graphics;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;

/**
 * This class represents a type of brick.
 * <p>The idea is that multiple bricks may share
 * the same type or change it at some moment and
 * this class adds a clean and easy way to avoid
 * code repetition, diamond inheritance and much more.
 * <br>
 * Hint: Take a look at the component pattern.
 * 
 * @author Momchil
 */
public abstract class BrickType {

    /*
     * This is used for storing all brick types.
     */
    private static final List<BrickType> brickTypes = new ArrayList<BrickType>();

    /**
     * This method lists all available brick types.
     * <p>It is useful when one wants for example to
     * choose a random brick type.
     * @return a list of all available brick types
     */
    public static List<BrickType> listBrickTypes() {
        return brickTypes;
    }

    /**
     * This brick type represents a destroyed brick.
     */
    public static final BrickType ID_NONE = null;

    /**
     * This brick type represents a brick that needs a
     * single hit to be destroyed. It does no special actions.
     */
    public static final BrickType ID_GREEN = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        public BrickType demote() {
            return ID_NONE;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_green.jpg");
        }
    };

    /**
     * This brick type represents a brick that when hit
     * is changed into a green brick. In essence it needs
     * to be hit twice in order to be destroyed.
     * @see #ID_GREEN
     */
    public static final BrickType ID_RED = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        public BrickType demote() {
            return ID_GREEN;
        }
        
        public Image allocateImage() {
            return ImageResources.getImage("brick_red.jpg");
        }
    };

    /**
     * This brick type represents a brick that cannot be
     * destroyed and nor does it need to be.
     */
    public static final BrickType ID_GREY = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return false;
        }

        public BrickType demote() {
            return ID_GREY;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_grey.jpg");
        }
    };

    /**
     * This brick type represents a brick that when destroyed
     * disappears but also destroys all surrounding bricks.
     */
    public static final BrickType ID_STAR = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        @Override
        public void onHit(Simulation simulation, Brick brick, Ball ball) {
            simulation.getBrickManager().destroySurroundingBricks(brick);
        }

        public BrickType demote() {
            return ID_NONE;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_star.jpg");
        }
    };

    /**
     * This brick type represents a brick which when hit disappears
     * and spawns a ball in it's place.
     */
    public static final BrickType ID_BALL = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        @Override
        public void onHit(Simulation simulation, Brick brick, Ball ball) {
            BallManager ballManager = simulation.getBallManager();
            ballManager.addBall().setTo(brick.x, brick.y);
        }

        public BrickType demote() {
            return ID_NONE;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_ball.jpg");
        }
    };

    /**
     * This brick type represents a brick which when hit
     * disappears and inverts the gravity in the game.
     */
    public static final BrickType ID_GRAVITY = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        @Override
        public void onHit(Simulation simulation, Brick brick, Ball ball) {
            PhysicsManager physicsManager = simulation.getPhysicsManager();
            physicsManager.distortGravity();
        }

        public BrickType demote() {
            return ID_NONE;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_gravity.jpg");
        }
    };

    /**
     * This brick type represents a brick which when hit
     * disappears and distorts the physics friction constant in the game.
     */
    public static final BrickType ID_FRICTION = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        @Override
        public void onHit(Simulation simulation, Brick brick, Ball ball) {
            PhysicsManager physicsManager = simulation.getPhysicsManager();
            physicsManager.distortSlide();
        }

        public BrickType demote() {
            return ID_NONE;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_friction.jpg");
        }
    };

    /**
     * This brick type represents a brick which when hit
     * disappears and distorts the physics bounce constant in the game.
     */
    public static final BrickType ID_BOUNCE = new BrickTypeImage() {
        public boolean needsToBeDestroyed() {
            return true;
        }

        @Override
        public void onHit(Simulation simulation, Brick brick, Ball ball) {
            PhysicsManager physicsManager = simulation.getPhysicsManager();
            physicsManager.distortBounce();
        }

        public BrickType demote() {
            return ID_NONE;
        }

        public Image allocateImage() {
            return ImageResources.getImage("brick_bounce.jpg");
        }
    };

    /*
     * This constructor adds the brick type to the list
     * of all brick types.
     */
    public BrickType() {
        brickTypes.add(this);
    }

    /**
     * This method determines whether the brick needs to be
     * destroyed in order for the player to win. This allows for
     * a brick that is normally destroyable to be left alive in
     * the game.
     * @return <code>true</code> if the player is required to
     * destroy the brick in order to win, <code>false</code>
     * otherwise.
     */
    public abstract boolean needsToBeDestroyed();

    /**
     * This method determines what happes when the brick gets hit.
     * @param simulation reference to the current simulation
     * @param brick reference to the brick that got hit
     * @param ball reference to the ball that hit the brick
     */
    public void onHit(Simulation simulation, Brick brick, Ball ball) {
    }

    /**
     * This method determines whether this brick can be destroyed.
     * @return <code>true</code> if this brick will be destroyed when
     * hit, <code>false</code> otherwise.
     */
    public boolean isDestroyable() {
        return (this != demote());
    }

    /**
     * This method returns the brick type that should be used
     * for the current brick after it has been hit.
     * <p>This method is required to return a brick type.<br>
     * The same brick type or <code>ID_NONE</code> are allowed.
     * However,
     * <p>Warning: Overriding methods should not perform any action
     * what so ever different than the required.
     * @return reference to the brick type that will substitute for
     * the current one after the brick gets hit.
     */
    public abstract BrickType demote();

    /**
     * This method displays the current brick.
     * @param g interface to the graphics handle.
     * @param brick reference to the brick to be displayed.
     */
    public abstract void display(Graphics g, Brick brick);

}

/*
 * This class extends the BrickType class to offer an
 * easy way of displaying a single-image brick.
 */
abstract class BrickTypeImage extends BrickType {

    private final Image image = allocateImage();

    public abstract Image allocateImage();

    public void display(Graphics g, Brick brick) {
        int brickX = (int)(brick.x - brick.getHalfWidth());
        int brickY = (int)(brick.y - brick.getHalfHeight());
        int brickWidth = (int)brick.getWidth();
        int brickHeight = (int)brick.getHeight();
        g.drawImage(image, brickX, brickY, brickWidth, brickHeight, null);
    }

}
